import argparse
import math
import array
import cv2
import numpy as np
import scipy
from matplotlib import pyplot as plt
from scipy.signal import savgol_filter

parser = argparse.ArgumentParser(description='图片的二值化预处理')
parser.add_argument('--lr_img_dir', type=str, default=r'C:\Users\Administrator\Desktop\dunhuangchaofen\Testset\Set5\X2',
                    help='path to low resolution image dir') #待上采样图片文件夹
parser.add_argument('--hr_img_dir', type=str, default=r'C:\Users\Administrator\Desktop\bicubic',
                    help='path to desired output path for Upsampled images') #结果保存路径，会自动生成存储结果的文件夹，如  X2result
parser.add_argument('--scale', type=int, default=10,
                    help='path to desired output dir for Upsampled images')#上采样倍率
args = parser.parse_args()

# 全局阈值
def threshold_By_OTSU(input_img_file):
    #image=cv2.imread(input_img_file)
    gray = cv2.cvtColor(input_img_file, cv2.COLOR_BGR2GRAY)   ##要二值化图像，必须先将图像转为灰度图
    ret, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 使用THRESH_OTSU方法,将阈值设置为0,会自动在灰度双峰之间寻找一个阈值
    # ret, binary = cv2.threshold(gray, ret-45, 255, cv2.THRESH_BINARY)  # 在自动检索的阈值上减去一个值
    return binary
    # print("threshold value %s" % ret)  #打印阈值，超过阈值显示为白色，低于该阈值显示为黑色
    # binary = cv2.resize(binary, None, fx=10, fy=10)
    # cv2.imshow("threshold", binary) #显示二值化图像
    # cv2.waitKey(0)
    # cv2.destroyAllWindows()

# 局部阈值
def jubuThreshold(input_img_file):
    #image = cv2.imread(input_img_file)
    # cv2.imshow("image", input_img_file)  # 显示原图像
    gray = cv2.cvtColor(input_img_file, cv2.COLOR_BGR2GRAY)
    binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 101, 5) # 自适应阈值,平均法
    # binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 25, 10)  # 自适应阈值,高斯法
    # binary = cv2.resize(binary, None, fx=10, fy=10)
    return binary
    # cv2.imshow("binary", binary)
    # cv2.waitKey(0)
    # cv2.destroyAllWindows()


def FillHole(img):
    # cv2.imwrite("im_in.png", img)
    # 复制 im_in 图像
    im_floodfill = img.copy()

    # Mask 用于 floodFill，官方要求长宽+2
    h, w = img.shape[:2]
    mask = np.zeros((h + 2, w + 2), np.uint8)

    # floodFill函数中的seedPoint对应像素必须是背景
    isbreak = False
    for i in range(im_floodfill.shape[0]):
        for j in range(im_floodfill.shape[1]):
            if (im_floodfill[i][j] == 0):
                seedPoint = (i, j)
                isbreak = True
                break
        if (isbreak):
            break

    # 得到im_floodfill 255填充非孔洞值
    cv2.floodFill(im_floodfill, mask, seedPoint, 255)

    # 得到im_floodfill的逆im_floodfill_inv
    im_floodfill_inv = cv2.bitwise_not(im_floodfill)

    # 把im_in、im_floodfill_inv这两幅图像结合起来得到前景
    im_out = img | im_floodfill_inv
    # print(type(im_out))
    return im_out

img_path='D:\\whitebubble\\machinelearning\\yolo\\yolov3\\runs\\detect\\databubble0630_model1_allpic\\crops\\bubble\\001.jpg'
lr_img = cv2.imread(img_path)

# 基于三次插值的图像重建
hr_img = cv2.resize(lr_img, (0, 0), fx=int(f"{args.scale}"), fy=int(f"{args.scale}"), interpolation=cv2.INTER_CUBIC)
cv2.imshow('hr_img',hr_img)
'''
blur_img = cv2.GaussianBlur(hr_img, (51,51),0)
cv2.imshow('blur_img', blur_img)
hr_img = cv2.addWeighted(hr_img, 1.5, blur_img, -0.5, 0)
cv2.imshow('addweighted',hr_img)
'''

# img2 = cv2.Sobel(hr_img, cv2.CV_64F, 0, 1, ksize=11)
# cv2.imshow('sobel', img2)

# 阈值分割
# binary = threshold_By_OTSU(hr_img) # 全局阈值
binary = jubuThreshold(hr_img) # 局部阈值
binary = cv2.bitwise_not(binary) # 二值化反转
cv2.imshow('binary',binary)

# 开运算 先腐蚀后膨胀
kernel = np.ones((5, 5), dtype=np.uint8)
close = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, 1)
cv2.imshow('OPEN', close)

# 删除连通域面积小于400的部分
_, labels, stats, centroids = cv2.connectedComponentsWithStats(close) # stats包含每个连通域外接矩形的左上角坐标x,y;w,h;s(像素个数)
i=0
for istat in stats:
    if istat[4]<400:
        if istat[3]>istat[4]:
            r=istat[3]
        else:r=istat[4]
        cv2.rectangle(close,tuple(istat[0:2]),tuple(istat[0:2]+istat[2:4]) , 0,thickness=-1)  # 26
    i=i+1
cv2.imshow('holefill_delate_smallpart',close)

# 图像填充
hole_fill = FillHole(close)
cv2.imshow('hole_fill',hole_fill)

contours, hierarchy = cv2.findContours(hole_fill, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(hr_img, contours, -1, (0,0,255), 3)
cv2.imshow('findcontours', hr_img)

cnt = contours[0]
hull = cv2.convexHull(cnt,returnPoints=False)
defects = cv2.convexityDefects(cnt, hull)
distance = []
for i in range(defects.shape[0]):
    s,e,f,d = defects[i,0]
    start = tuple(cnt[s][0])
    end = tuple(cnt[e][0])
    farest_point = tuple(cnt[f][0])
    distance.append(d/256.0)
    cv2.line(hr_img, start, end, [0, 255, 0], 2)
    cv2.circle(hr_img, farest_point, 3, [255,0,0], -1)
cv2.imshow('img', hr_img)

for i in range(hull.shape[0]):
    if i == 0:
        start = (hull[hull.shape[0]-1,0,0],hull[hull.shape[0]-1,0,1])
    else:
        start = (hull[i-1,0,0],hull[i-1,0,1])
    end = (hull[i,0,0],hull[i,0,1])
    # far = tuple(cnt[f][0])
    cv2.line(hr_img, start, end, [0, 255, 0], 2)
    # cv2.circle(img, far, 5, [0, 0, 255], -1)
cv2.imshow('img', hr_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

# 边缘检测
canny = cv2.Canny(hole_fill, 100, 200) #像素的梯度大于200，那么定义为边缘，若梯度小于100，则其为非边缘
cv2.imshow('canny',canny)

"""

"""

# 基于凹凸变化的凹点检测
# 获取质心
mass_x, mass_y = np.where(canny >= 255)
# mass_x and mass_y are the list of x indices and y indices of mass pixels
cent_x = np.average(mass_x)
cent_y = np.average(mass_y) # 计算质心位置
center = canny.copy()
center[(int(cent_x),int(cent_y))] = 255
cv2.imshow('center', center)
print(cent_x,cent_y)

# 按照逆时针顺序，对边界点进行排序。原理：每个边界点对于质心计算atan,并排序
x, y = np.nonzero(canny)
points = list(zip(x, y))
points.sort(key=lambda m:math.atan2(m[1]-cent_y,m[0]-cent_x))
points = np.array(points) # points是所有轮廓点
print(points)

# 计算边界点到质心的距离,并绘制图像
distance=[]
num = len(points[:,0])
for i in range(num):
    distance.append(pow(pow(points[i,0]-cent_x,2)+pow(points[i,1]-cent_y,2),0.5))
# print(distance)
plt.scatter(list(range(num)), distance)
plt.show()

# 平滑处理
window = np.ones(int(30)) / float(30)
re = np.convolve(distance, window, 'same')
plt.plot(list(range(num)), re, 'b', label = 'savgol')
plt.show()

# 极小值点检测
valley, properties = scipy.signal.find_peaks((-re), prominence=(5, None)) # promonence设置凸起的阈值，注意这里用的是反转的 -re
print(valley)   # valley是极小值点的位置
extreme_point = canny.copy() # 绘制检测到的极小值点
for i in range(len(valley)):
    cv2.circle(extreme_point,[(points[valley[i]])[1],(points[valley[i]])[0]],2,(255,0,0),-1)
cv2.imshow('extreme_point',extreme_point)

# 分割路径
edge_clone = hr_img.copy()
for i in range(len(valley)):
    curve = []
    if i == len(valley)-1:
        curve = np.vstack((points[valley[i]:],points[0:valley[0]])) # 当为最后一段曲线时，用最后一段曲线加上开头的一段
        # plt.plot(curve[:,0], curve[:,1], 'b', label='savgol')
        # plt.show()
    else:
        curve = points[valley[i]:valley[i+1]]
        # plt.plot(curve[:, 0], curve[:, 1], 'b', label='savgol')
        # plt.show()
    # print(curve)
    # 椭圆拟合
    _ellipse = cv2.fitEllipse(curve)  # 椭圆拟合
    print(_ellipse)  # 这儿包含 椭圆的中心坐标，长短轴长度（2a，2b），旋转角度
    # edge_clone = canny.copy() # 椭圆坐标对应需要改
    # cv2.ellipse(edge_clone, [[_ellipse[0][1],_ellipse[0][0]],[_ellipse[1][1],_ellipse[1][0]],_ellipse[2]], (100, 100, 100), 5)
    cv2.ellipse(edge_clone, [[_ellipse[0][1],_ellipse[0][0]],[_ellipse[1][1],_ellipse[1][0]],_ellipse[2]], (100, 100, 100), 5) # 椭圆坐标对应需要改 xy是反的
    # cv2.destroyAllWindows()
cv2.imshow('1',edge_clone)


cv2.waitKey(0)
cv2.destroyAllWindows()